import 'package:analyzer/dart/constant/value.dart';
import 'package:source_gen/source_gen.dart';
import 'package:sqliteasy_core/sqliteasy_core.dart' hide ColumnInfo, TableInfo, ForeignKeyInfo, IndexInfo;
import 'package:analyzer/dart/element/type.dart';
import '../errors.dart';
import 'ext.dart';
import 'dart:core';
import '../log.dart';
import 'util.dart';

import '../model/database.dart';
import 'package:analyzer/dart/element/element.dart';

class DatabaseUtil {
  static String _tag = "DatabaseUtil";
  static TypeChecker _entityTypeChecker = TypeChecker.fromRuntime(Entity);
  static TypeChecker _databaseTypeChecker = TypeChecker.fromRuntime(Database);
  static TypeChecker _columnTypeChecker = TypeChecker.fromRuntime(Column);
  static TypeChecker _ignoreTypeChecker = TypeChecker.fromRuntime(Ignore);
  static TypeChecker _primaryKeyChecker = TypeChecker.fromRuntime(PrimaryKey);
  static TypeChecker _indexChecker = TypeChecker.fromRuntime(Index);

  static final conflictStrategyMap = {
    ConflictStrategy.abort: "ABORT",
    ConflictStrategy.ignore: "IGNORE",
    ConflictStrategy.replace: "REPLACE"
  };

  static final _daoChecker = TypeChecker.fromRuntime(Dao);
  static final _insertChecker = TypeChecker.fromRuntime(Insert);
  static final _updateChecker = TypeChecker.fromRuntime(Update);
  static final _deleteChecker = TypeChecker.fromRuntime(Delete);
  static final _queryChecker = TypeChecker.fromRuntime(Query);
  static final _daoActionChecker = TypeChecker.fromRuntime(DaoAction);

  /// 该Element是否是一个DaoClass
  static bool isDaoClass(Element element) {
    return element is ClassElement && _daoChecker.hasAnnotationOf(element);
  }

  static Map<ClassElement, EntityInfo> _entityInfoCache = Map();

  static DatabaseInfo getDatabaseInfoFromElement(ClassElement databaseElement) {
    final databaseObject = _databaseTypeChecker.firstAnnotationOf(databaseElement);
    Database database = databaseObject.toDatabase();
    if (databaseObject == null) {
      throw SQLiteasyBuildError("The database class must be annotated with the Database Annotation");
    }
    // 获取Database注解的entities属性，拿到这个数据库中的所有实体类，最终用于建表
    var entities = databaseObject.getField("entities")?.toListValue();
    if (entities == null) {
      return null;
    }
    var entityList = entities.map((e) => getEntityInfoFromElement(e.toTypeValue().element))?.toList();

    var views = databaseObject
        .getField("views")
        ?.toListValue()
        ?.map((e) => getViewInfoFromElement(e.toTypeValue().element))
        ?.toList();

    return DatabaseInfo(databaseElement, entityList, database.version,
        exportSchema: database.exportSchema, views: views, nullValueStrategy: database.nullValueStrategy);
  }

  static EntityInfo getEntityInfoFromElement(ClassElement entityElement) {
    final tag = "getEntityInfoFromElement";
    if (entityElement == null) return null;
    if (_entityInfoCache.containsKey(entityElement)) {
      return _entityInfoCache[entityElement];
    }
    // 解析实体类，返回实体类信息
    print("process entity: ${entityElement.runtimeType}, source:${entityElement.location.components}");
    List<ColumnInfo> columnList = entityElement.fields.map((e) => getColumnInfoFromField(e))?.toList();
    columnList.removeWhere((element) => element == null);
    List<ColumnInfo> usedColumns = List<ColumnInfo>();
    List<ColumnInfo> ignoredColumns = List<ColumnInfo>();
    List<ColumnInfo> primaryColumns = List<ColumnInfo>();

    DartObject entityObject = _entityTypeChecker.firstAnnotationOf(entityElement);
    Entity entity = entityObject?.toEntity();
    bool hasAutoIncrementKey = _processColumns(entity, columnList, ignoredColumns, usedColumns, primaryColumns);
    if (primaryColumns.isEmpty) {
      if (entity == null) {
        throw SQLiteasyBuildError("The entity(${entityElement.name}) must have primary key");
      }
      log(tag, "该实体类没有使用PrimaryKey注解，从Entity注解中的primaryKeys中获取主键信息");
      // 该实体类没有使用PrimaryKey注解
      List<ColumnInfo> primaryColumnArray = _extractPrimaryColumnsFromEntity(entity, entityElement, usedColumns);
      primaryColumns = primaryColumnArray;
    } else {
      if (hasAutoIncrementKey && primaryColumns.length > 1) {
        throw SQLiteasyBuildError("设置主键自增后不能再设置联合主键");
      }
      // 根据Column注解中的primaryKeyPosition属性对primary进行重排序
      List<ColumnInfo> tempPrimaryKeys = List(primaryColumns.length);
      int curIndex = 0;
      primaryColumns.forEach((element) {
        int index = element.primaryKeyPosition ?? curIndex;
        if (index < 0 || index >= tempPrimaryKeys.length) {
          throw SQLiteasyBuildError("${entityElement.name}::${element.field}的PrimaryKeyPosition数组越界");
        }
        if (tempPrimaryKeys[index] != null) {
          throw SQLiteasyBuildError("Entity主键顺序出现冲突");
        }
        tempPrimaryKeys[index] = element;
        curIndex++;
      });
      primaryColumns = tempPrimaryKeys;
    }

    if (entity == null) {
      return EntityInfo(entityElement.location.components[0], entityElement.name,
          SQLiteasyUtil.humpToUnderline(entityElement.name), usedColumns,
          ignoredColumns: ignoredColumns, primaryKeys: primaryColumns);
    } else {
      final tableName = entity.tableName ?? SQLiteasyUtil.humpToUnderline(entityElement.name);
      List<IndexInfo> indices = _getIndices(entityObject, entityElement) ?? [];
      indices.forEach((element) {
        element.name ??= getDefaultIndexName(tableName, element.columns);
        element.unique ??= false;
      });

      List<ForeignKeyInfo> foreignKeys =
          entityObject.getField("foreignKeys")?.toListValue()?.map((e) => e.toForeignKeyInfo())?.toList() ?? [];
      foreignKeys.forEach((element) {
        element.onDelete ??= Action.NO_ACTION;
        element.onUpdate ??= Action.NO_ACTION;
      });

      final removed = columnList.where((element) => entity.ignoredColumns?.contains(element.name) == true);
      ignoredColumns.addAll(removed);
      columnList.removeWhere((element) => entity.ignoredColumns?.contains(element.name) == true);

      final info = EntityInfo(
        entityElement.location.components[0],
        entityElement.name,
        tableName,
        usedColumns,
        indices: indices,
        foreignKeys: foreignKeys,
        ignoredColumns: ignoredColumns,
        primaryKeys: primaryColumns,
        inheritSuperIndices: entity.inheritSuperIndices,
      );
      _entityInfoCache[entityElement] = info;
      _checkIndices(info, indices);
      _checkForeignKeys(info, foreignKeys);
      return info;
    }
  }

  static List<IndexInfo> _getIndices(DartObject entityObject, Element entityElement) {
    final paramIndices = entityObject.getField("indices")?.toListValue()?.map((e) => e.toIndexInfo())?.toList();
    final annotatedIndices = _indexChecker.annotationsOf(entityElement)?.map((e) => e.toIndexInfo())?.toList();
    List<IndexInfo> result = [];
    if (paramIndices != null) {
      result.addAll(paramIndices);
    }
    if (annotatedIndices != null) {
      result.addAll(annotatedIndices);
    }
    return result;
  }

  static _checkIndices(EntityInfo entityInfo, List<IndexInfo> indices) {
    Set existedIndices = Set();
    indices.forEach((element) {
      final indexName = element.name ?? getDefaultIndexName(entityInfo.tableName, element.columns);
      if (element.columns?.isNotEmpty != true) {
        throw SQLiteasyBuildError("[${entityInfo.entityName}] the columns of index can not be empty.");
      }
      if (indexName.isEmpty) {
        throw SQLiteasyBuildError("[${entityInfo.entityName}] the name of index can not be empty.");
      }
      if (existedIndices.contains(indexName)) {
        // found declared index.
        throw SQLiteasyBuildError(
            "[${entityInfo.entityName}] There are multiple indices with name $indexName." +
                " This happen if you've declared the same index multiple times or different indices have the same name.",
            entityInfo.location);
      }
      existedIndices.add(indexName);
    });
  }

  static _checkForeignKeys(EntityInfo entityInfo, List<ForeignKeyInfo> keys) {
    if (keys == null) {
      return;
    }
    final columnSet = entityInfo.columns.map((e) => e.name).toSet();
    keys.forEach((element) {
      if (element.columns?.isNotEmpty != true || element.referenceColumns?.isNotEmpty != true) {
        throw SQLiteasyBuildError(
            "[${entityInfo.entityName}] the columns and parentColumns of foreign key can not be empty.",
            entityInfo.location);
      }
      if (element.columns.length != element.referenceColumns.length) {
        throw SQLiteasyBuildError(
            "[${entityInfo.entityName}] the columns.length of foreign key must equal to the referenceColumns.length.",
            entityInfo.location);
      }
      element.columns.forEach((element) {
        if (!columnSet.contains(element)) {
          throw SQLiteasyBuildError("[${entityInfo.entityName}] the entity ${entityInfo.entityName} does not have the column '$element'");
        }
      });

      final refInfo = element.entity;
      Set<String> parentColumnSet = Set();
      refInfo.columns.forEach((element) {
        parentColumnSet.add(element.name);
      });
      element.referenceColumns.forEach((parentColumn) {
        if (!parentColumnSet.contains(parentColumn)) {
          throw SQLiteasyBuildError(
              "[${entityInfo.entityName}] the entity ${refInfo.entityName} does not have the column '$parentColumn'",
              entityInfo.location);
        }
      });
      Set<String> uniqueConstraints = Set();
      uniqueConstraints.add(getColumnSig(refInfo.primaryKeys.map((e) => e.name).toList()));
      refInfo.indices.forEach((element) {
        if (element.unique == true) {
          uniqueConstraints.add(getColumnSig(element.columns));
        }
      });
      if (!uniqueConstraints.contains(getColumnSig(element.referenceColumns))) {
        final childLocation = "${entityInfo.location}->${entityInfo.entityName}";
        final parentLocation = "${refInfo.location}->${refInfo.entityName}";
        throw SQLiteasyBuildError("${childLocation} has a foreign key (${element.columns.join(",")})" +
            " that references" +
            " ${parentLocation} (${element.referenceColumns.join(",")})" +
            " but ${parentLocation} does not have a unique index on those columns nor the columns are its primary key." +
            " SQLite requires having a unique constraint on referenced parent columns so you must add a unique index to $parentLocation that has (${element.referenceColumns.join(",")}) column(s).");
      }
    });
  }

  static bool _processColumns(Entity entity, List<ColumnInfo> columnList, List<ColumnInfo> ignoredColumns,
      List<ColumnInfo> usedColumns, List<ColumnInfo> primaryColumns) {
    // 是否有自增主键
    bool hasAutoIncrementKey = false;
    columnList.forEach((element) {
      if (element.ignored == true) {
        ignoredColumns.add(element);
      } else {
        usedColumns.add(element);
        if (element.isPrimaryKey == true) {
          if (entity?.primaryKeys?.isNotEmpty == true) {
            throw SQLiteasyBuildError("当实体类使用@PrimaryKey注解后不能再使用Entity注解的primaryKeys指定主键");
          }
          if (element.autoGenerate == true) {
            hasAutoIncrementKey = true;
          }
          primaryColumns.add(element);
        }
      }
    });
    return hasAutoIncrementKey;
  }

  static List<ColumnInfo> _extractPrimaryColumnsFromEntity(
      Entity entity, ClassElement entityElement, List<ColumnInfo> usedColumns) {
    log("extract primary columns from entity(${entity.tableName})", _tag);
    if (entity?.primaryKeys?.isNotEmpty != true) {
      throw SQLiteasyBuildError("the entity(${entityElement.name}) must have primary key");
    }
    // primaryKeys不可能为空
    Set<String> primaryKeySet = Set();
    primaryKeySet.addAll(entity.primaryKeys);
    final primaryColumnArray = List<ColumnInfo>(entity.primaryKeys.length);
    int currentIndex = 0;
    usedColumns.forEach((element) {
      if (primaryKeySet.contains(element.name)) {
        primaryKeySet.remove(element.name);
        element.isPrimaryKey = true;
        int targetIndex = element.primaryKeyPosition ?? currentIndex;
        if (targetIndex < 0 || targetIndex >= primaryColumnArray.length) {
          throw SQLiteasyBuildError("${entityElement.name}::${element.field}的PrimaryKeyPosition数组越界");
        }
        if (primaryColumnArray[targetIndex] != null) {
          throw SQLiteasyBuildError("Entity主键的顺序出现冲突");
        }
        primaryColumnArray[targetIndex] = element;
        currentIndex++;
      }
    });
    if (primaryKeySet.isNotEmpty) {
      throw SQLiteasyBuildError("Entity注解primaryKeys属性中声明的部分Column不存在");
    }
    return primaryColumnArray;
  }

  static ViewInfo getViewInfoFromElement(ClassElement element) {
    // TODO:Implement the method
    return null;
  }

  static ColumnInfo getColumnInfoFromField(FieldElement element) {
    DartObject columnObject = _columnTypeChecker.firstAnnotationOf(element);

    ColumnInfo info;
    if (columnObject == null) {
      if (element.isPrivate) {
        return null;
      }
      info = ColumnInfo(
        element,
        SQLiteasyUtil.humpToUnderline(element.name),
        type: getSQLiteType(element.type),
      );
    } else {
      if (element.isPrivate) {
        throw SQLiteasyBuildError("${element.enclosingElement.name}::${element.name} -> a column field must be public");
      }
      final columnInfo = columnObject.toColumnInfo();
      columnInfo.field = element;
      if (columnInfo.name?.isNotEmpty != true) {
        columnInfo.name = SQLiteasyUtil.humpToUnderline(element.name);
      }
      if (columnInfo.type?.isNotEmpty != true) {
        columnInfo.type = getSQLiteType(element.type);
      }
      info = columnInfo;
    }

    DartObject primaryKeyObject = _primaryKeyChecker.firstAnnotationOf(element);
    PrimaryKeyInfo primaryKeyInfo;
    if (primaryKeyObject != null) {
      primaryKeyInfo = primaryKeyObject.toPrimaryKeyInfo();
    }
    if (primaryKeyInfo != null) {
      info.isPrimaryKey = primaryKeyInfo != null;
      if (info.primaryKeyPosition == null && info.isPrimaryKey == true) {
        info.primaryKeyPosition = primaryKeyInfo?.position ?? 0;
      }
      info.autoGenerate = primaryKeyInfo?.autoGenerate;
      if (info.autoGenerate == true && info.type != SQLiteTypes.integer) {
        throw SQLiteasyBuildError(
            "处理Field(${element.enclosingElement.name}::${element.name})时出错，自增主键的值类型必须为${SQLiteTypes.integer}");
      }
    }

    if (_ignoreTypeChecker.hasAnnotationOf(element)) {
      info.ignored = true;
    }
    return info;
  }

  static String getSQLiteType(DartType type) {
    final typeAdapter = TypeAdapterFactory.getAdapterByKey(type.toString().replaceAll("*", ""));
    if (typeAdapter == null) {
      throw SQLiteasyBuildError("Unsupported Type:${type}");
    }
    return typeAdapter.sqliteType.typeName;
  }

  static String getDatabaseImplName(String name) {
    return "${name}Impl\$_\$";
  }

  static DartType getDaoMethodReturnEntityType(DartType type) {
    print("getDaoMethodReturnEntityType:${type}");
    return getEntityType(getDaoMethodReturnType(type));
  }

  static DartType getDaoMethodReturnType(DartType type) {
    print("getDaoMethodReturnType:${type}");
    if (type == null || type.isVoid) {
      return null;
    }
    if (!type.isDartAsyncFuture) {
      throw SQLiteasyBuildError("The type of dao method return value must be Future");
    }

    final parameterizedType = type as ParameterizedType;
    if (parameterizedType.typeArguments.isEmpty || parameterizedType.typeArguments[0].isVoid) {
      return null;
    }
    return parameterizedType.typeArguments[0];
  }

  static DartType getEntityType(DartType type) {
    if (type == null || type.isVoid) {
      return type;
    }
    if (type.isDartCoreList || type.isDartCoreSet) {
      // batchInsert
      final typeArguments = (type as ParameterizedType).typeArguments;
      if (typeArguments.isEmpty) {
        throw SQLiteasyBuildError("the type argument count of Iterable can not be zero");
      }
      final typeArg = typeArguments[0];
      if (typeArg.isDartCoreInt) {
        return type;
      }
      return typeArguments[0];
    } else {
      return type;
    }
  }

  static String getDaoImplName(Element element) {
    return "${element.name}Impl\$_\$";
  }

  static String getValueConverterName(EntityInfo entityInfo) {
    return "_${entityInfo.entityName}ValueConverter";
  }

  static String getTypeName(DartType type) {
    return type.toString().replaceAll("*", "");
  }

  static bool isCollectionType(DartType type) {
    return type.isDartCoreList || type.isDartCoreSet;
  }

  static String getColumnSig(List<String> columnNames, {bool orderRelated = false}) {
    final columns = List.from(columnNames);
    if (!orderRelated) {
      columns.sort();
    }
    return columns.join(",");
  }

  static String getDefaultIndexName(String tableName, List<String> columns) {
    return "index_${tableName}_${columns.join("_")}";
  }
}
